In [1]:
f%%html
<script src="https://rawgithub.com/mrdoob/three.js/master/build/three.js"></script>
In [2]:
%%javascript
require(["notebook/js/widget"], function() {
var GeometryModel = Backbone.AssociatedModel.extend({});
IPython.widget_manager.register_widget_model('GeometryModel', GeometryModel);
var SphereGeometryModel = Backbone.AssociatedModel.extend({});
IPython.widget_manager.register_widget_model('SphereGeometryModel', SphereGeometryModel);
var MaterialModel = Backbone.AssociatedModel.extend({});
IPython.widget_manager.register_widget_model('MaterialModel', MaterialModel);
var MeshModel = Backbone.AssociatedModel.extend({
relations: [
{type: Backbone.One,
key: 'geometry',
relatedModel:SphereGeometryModel},
{type: Backbone.One,
key: 'material',
relatedModel:MaterialModel},
],
});
IPython.widget_manager.register_widget_model('MeshModel', MeshModel);
var CameraModel = Backbone.AssociatedModel.extend({});
IPython.widget_manager.register_widget_model('CameraModel', CameraModel);
var SceneModel = Backbone.AssociatedModel.extend({
relations: [
{type: Backbone.Many,
key: 'meshes',
relatedModel:MeshModel},
],
});
IPython.widget_manager.register_widget_model('SceneModel', SceneModel);
var RendererModel = IPython.WidgetModel.extend({
relations: [
{type: Backbone.One,
key: 'scene',
relatedModel:SceneModel},
{type: Backbone.One,
key: 'camera',
relatedModel:CameraModel},
],
});
IPython.widget_manager.register_widget_model('RendererModel', RendererModel);
})
Traitlet objects in python, and some way to specify the scene graph relationship. Possibly the scene graph is represented separately from the collection of objects, and possibly the objects form a nested scenegraph
Communication with the front end over a single comm link---so the renderer is the only "widget". Changes to each of the objects is communicated through it.
Events on the javascript side
have a flat set of objects and a separate scenegraph showing the relationships between the objects.
nested set of objects
In [2]:
In [2]:
In [3]:
# Import the base Widget class and the traitlets Unicode class.
from IPython.html.widgets.widget import Widget, NonDOMWidget
from IPython.utils.traitlets import Unicode, Int, Instance, Enum, List, Float
class Geometry(NonDOMWidget):
target_name = Unicode('GeometryModel')
default_view_name = Unicode('GeometryView')
class SphereGeometry(Geometry):
target_name = Unicode('SphereGeometryModel')
default_view_name = Unicode('SphereGeometryView')
_keys = ['radius']
radius = Int(100)
class Material(NonDOMWidget):
target_name = Unicode('MaterialModel')
default_view_name = Unicode('MaterialView')
_keys = ['color']
color = Int(0x00cc00)
class Mesh(NonDOMWidget):
target_name = Unicode('MeshModel')
default_view_name = Unicode('MeshView')
_keys = ['geometry', 'material']
geometry = Instance(Geometry)
material = Instance(Material)
class Camera(NonDOMWidget):
target_name = Unicode('CameraModel')
default_view_name = Unicode('CameraView')
_keys = ['fov', 'ratio']
fov = Int(70)
ratio = Float(600.0/400.0)
class Scene(NonDOMWidget):
target_name = Unicode('SceneModel')
default_view_name = Unicode('SceneView')
_keys = ['meshes']
meshes = List(Instance(Mesh))
class Renderer(Widget):
target_name = Unicode('RendererModel')
default_view_name = Unicode('RendererView')
_keys = ['width', 'height', 'renderer_type', 'scene', 'camera']
width = Int(600)
height = Int(400)
renderer_type = Enum(['webgl', 'canvas', 'auto'], 'auto')
scene = Instance(Scene)
camera = Instance(Camera)
In [4]:
%%javascript
require(["notebook/js/widget"], function() {
var RendererView = IPython.WidgetView.extend({
render : function(){
var width = this.model.get('width');
var height = this.model.get('height');
this.renderer = new THREE.WebGLRenderer();
this.renderer.setSize( width, height);
this.$el.empty().append( this.renderer.domElement );
this.camera = new CameraView({model: this.model.get('camera')});
this.camera.render();
this.scene = new SceneView({model: this.model.get('scene')});
this.scene.render();
console.log('renderer', this.model, this.scene.obj);
},
update : function(){
console.log('update');
// TODO: tie into a requestAnimationFrame update (just trigger a frame if one is not already lined up)
this.renderer.render(this.scene.obj, this.camera.obj);
return IPython.WidgetView.prototype.update.call(this);
// perhaps we should just listen to the nested-change event
// and perform the appropriate modificatino ourselves?
// this assumes that (1) we've stored the three.js hierarchy exactly as the models are set up
// and (2) all changes to the three.js objects are exactly the same (e.g., setting attributes)
// regardless, we should probably listen to the nested-change event to trigger a redraw
},
});
IPython.widget_manager.register_widget_view('RendererView', RendererView);
var NestedView = function(options) {
this.cid = _.uniqueId('view');
options || (options = {});
_.extend(this, _.pick(options, ['model', 'id']));
this.initialize.apply(this, arguments);
this.model.on('change', this.update, this);
this.visible = true;
};
_.extend(NestedView.prototype, Backbone.Events, {
initialize: function(){},
render: function() {
return this;
},
remove: function() {
this.stopListening();
return this;
},
});
NestedView.extend = Backbone.View.extend;
var CameraView = NestedView.extend({
render: function() {
var camera = this.obj = new THREE.PerspectiveCamera( this.model.get('fov'), this.model.get('ratio'), 1, 1000 );
camera.position.set(0,150,400);
return camera;
}
});
var SceneView = NestedView.extend({
render: function() {
var scene = this.obj = new THREE.Scene();
var light = new THREE.PointLight(0xffffff);
light.position.set(100,250,100);
scene.add(light);
var that = this;
this.meshviews = [];
this.model.get('meshes').each(function(model) {
var m = new MeshView({model: model})
that.meshviews.push(m)
scene.add(m.render());
})
this.model.get('lights').each(function(model) {
var m = new LightView({model: model})
that.lightviews.push(m);
scene.add(m.render());
})
return scene;
}
});
// TODO: most change events are very similar---just change the three.js object appropriately.
// maybe
var GeometryView = NestedView.extend({
render: function() {
var geometry = this.obj = new THREE.SphereGeometry(this.model.get('radius'), 32, 16);
return geometry;
}
// update will need to generate a new mesh (to change the radius...)
})
var MaterialView = NestedView.extend({
render: function() {
var material = this.obj = new THREE.MeshLambertMaterial({color: this.model.get('color')});
return material;
}
})
var MeshView = NestedView.extend({
render: function() {
this.geometryview = new GeometryView({model: this.model.get('geometry')});
this.materialview = new MaterialView({model: this.model.get('material')});
var mesh = this.obj = new THREE.Mesh( this.geometryview.render(), this.materialview.render() );
return mesh
}
});
});
In [5]:
from IPython.display import display
sphere = Mesh(geometry=SphereGeometry(), material=Material())
scene = Scene(meshes=[sphere])
renderer = Renderer(camera=Camera(), scene = scene)
renderer.get_state()
display(renderer)
In [ ]: